﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Elentok.Common.Collections
{
  /// <summary>
  /// Defines the read-only version of a <see cref="Dictionary&lt;TKey,TValue&gt;"/> (only allows to read that data).
  /// </summary>
  /// <typeparam name="TKey">The type of the dictionary keys</typeparam>
  /// <typeparam name="TValue">The type of the dictionary values</typeparam>
  public interface IReadOnlyDictionary<TKey, TValue> : IEnumerable<KeyValuePair<TKey, TValue>>
  {
    /// <summary>
    /// Gets a collection containing the keys in the dictionary.
    /// </summary>
    ICollection<TKey> Keys { get; }

    /// <summary>
    /// Gets a collection containing the values in the dictionary.
    /// </summary>
    ICollection<TValue> Values { get; }

    /// <summary>
    /// Gets the value for <paramref name="key"/>
    /// </summary>
    /// <param name="key">The key of the value to get</param>
    /// <returns></returns>
    TValue this[TKey key] { get; }

    /// <summary>
    /// Gets the value associated with the specified key (throws an exception if not found).
    /// </summary>
    /// <param name="key">The key of the value to get</param>
    /// <param name="value">If the key is found, this parameter will contain the value of the key.</param>
    /// <returns></returns>
    /// <exception cref="System.ArgumentNullException" />
    bool TryGetValue(TKey key, out TValue value);

    /// <summary>
    /// Gets the value associated with the specified key
    /// (if the key does not exist it returns <paramref name="defaultValue"/>).
    /// </summary>
    /// <param name="key">The key of the value to get</param>
    /// <param name="defaultValue">If the key is not found, return this value.</param>
    /// <returns></returns>
    TValue GetValue(TKey key, TValue defaultValue);
  }

  /// <summary>
  /// Read-only version of a <see cref="Dictionary&lt;TKey,TValue&gt;"/> (only allows to read that data).
  /// </summary>
  /// <typeparam name="TKey">The type of the dictionary keys</typeparam>
  /// <typeparam name="TValue">The type of the dictionary values</typeparam>
  public class ReadOnlyDictionary<TKey, TValue> : IReadOnlyDictionary<TKey, TValue>
  {
    /// <summary>
    /// Reference to the editable version of this dictionary
    /// </summary>
    private Dictionary<TKey, TValue> _editableDictionary;

    /// <summary>
    /// Creates an instance of <see cref="ReadOnlyDictionary&lt;TKey,TValue&gt;"/> as a read-only
    /// version of <paramref name="editableDictionary"/>.
    /// </summary>
    /// <param name="editableDictionary">The editable dictionary to reference</param>
    public ReadOnlyDictionary(Dictionary<TKey, TValue> editableDictionary)
    {
      _editableDictionary = editableDictionary;
    }

    #region IReadOnlyDictionary<TKey,TValue> Members

    /// <summary>
    /// Gets a collection containing the keys in the dictionary.
    /// </summary>
    public ICollection<TKey> Keys
    {
      get { return _editableDictionary.Keys; }
    }

    /// <summary>
    /// Gets a collection containing the values in the dictionary.
    /// </summary>
    public ICollection<TValue> Values
    {
      get { return _editableDictionary.Values; }
    }

    /// <summary>
    /// Gets the value for <paramref name="key"/>.
    /// </summary>
    /// <param name="key">The key of the value to get</param>
    /// <returns></returns>
    public TValue this[TKey key]
    {
      get { return _editableDictionary[key]; }
    }

    /// <summary>
    /// Gets the value associated with the specified key (throws an exception if not found).
    /// </summary>
    /// <param name="key">The key of the value to get</param>
    /// <param name="value">If the key is found, this parameter will contain the value of the key.</param>
    /// <returns></returns>
    /// <exception cref="System.ArgumentNullException" />
    public bool TryGetValue(TKey key, out TValue value)
    {
      return _editableDictionary.TryGetValue(key, out value);
    }

    /// <summary>
    /// Gets the value associated with the specified key
    /// (if the key does not exist it returns <paramref name="defaultValue"/>).
    /// </summary>
    /// <param name="key">The key of the value to get</param>
    /// <param name="defaultValue">If the key is not found, return this value.</param>
    /// <returns></returns>
    public TValue GetValue(TKey key, TValue defaultValue)
    {
      TValue value;
      if (_editableDictionary.TryGetValue(key, out value))
        return value;
      else
        return defaultValue;
    }
    #endregion

    #region IEnumerable<KeyValuePair<TKey,TValue>> Members
    /// <summary>
    /// Returns an enumerator that iterates through <see cref="KeyValuePair&lt;TKey,TValue&gt;"/>
    /// </summary>
    /// <returns></returns>
    public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
    {
      return _editableDictionary.GetEnumerator();
    }

    #endregion

    #region IEnumerable Members

    /// <summary>
    /// Returns an enumerator that iterates through <see cref="KeyValuePair&lt;TKey,TValue&gt;"/>
    /// </summary>
    /// <returns></returns>
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
    {
      return _editableDictionary.GetEnumerator();
    }

    #endregion
  }

  /// <summary>
  /// Extensions to collections and lists regarding pangu dictionaries
  /// </summary>
  public static class DictionaryExtensions
  {
    /// <summary>
    /// Returns a readonly version of the given dictionary
    /// (each call returns a new instance of the readonly dictionary)
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    /// <typeparam name="TValue"></typeparam>
    /// <param name="editableDictionary"></param>
    /// <returns></returns>
    public static IReadOnlyDictionary<TKey, TValue> AsReadOnly<TKey, TValue>(this Dictionary<TKey, TValue> editableDictionary)
    {
      return new ReadOnlyDictionary<TKey, TValue>(editableDictionary);
    }
  }


}
